// -----------------------------
// Author: 		Dhaenens Gert	
// Creation: 	10/22/2014 5:36:04 PM				
// -----------------------------

#include "TerrainGenerator.h"

#include "Application.h"
#include "RenderManager.h"

#include "Mesh.h"
#include "Plane.h"

#include "Filters.h"

ITexture2D* TerrainGenerator::Generate(Application* lpApp, const TerrainDesc& desc, Float** lpOutputData)
{
	// Generate the Data
	Float* lpData = GenerateData(lpApp, desc);

	// Create the Texture
	ITexture2D* lpTexture = ITexture2D::Create(lpApp->GetRenderer()->GetDevice(), desc.Subdivisions + 1, desc.Subdivisions + 1, Format::Float, lpData);

	if (lpOutputData)
		(*lpOutputData) = lpData;
	else
		delete[] lpData;

	return lpTexture;
}

Float* TerrainGenerator::GenerateData(Application* lpApp, const TerrainDesc& desc)
{
	UInt32 subDiv = desc.Subdivisions + 1;

	// Create the Data
	Float* lpData = new Float[subDiv * subDiv];
	for (UInt32 i = 0; i < subDiv * subDiv; ++i)
		lpData[i] = 0.0f;

	// Generate the 4 courner points
	Float initialOffset = desc.BaseOffset;
	lpData[0] = initialOffset;
	lpData[subDiv - 1] = initialOffset;
	lpData[(subDiv - 1) * subDiv] = initialOffset;
	lpData[subDiv * subDiv - 1] = initialOffset;

	UInt32 childDim = desc.Subdivisions;
	UInt32 childDepth = 1;
	UInt32 depth = 1;
	Float roughness = desc.Rougness;
	while (childDim > 1)
	{
		childDepth = (UInt32)Math::Pow(2.0f, (Int32)(depth - 1));

		for (UInt32 y = 0; y < childDepth; ++y)
		{
			for (UInt32 x = 0; x < childDepth; ++x)
			{
				// Calculate the Square Indices
				UInt32 lbi = (y * childDim) + (x * (subDiv * childDim));
				UInt32 lti = lbi + childDim;
				UInt32 rbi = (y * childDim) + ((x + 1) * (subDiv * childDim));
				UInt32 rti = rbi + childDim;

				// Calculate the Diamond Indices
				UInt32 ld = lbi + (childDim / 2);
				UInt32 rd = rbi + (childDim / 2);
				UInt32 td = lti + ((childDim / 2) * subDiv);
				UInt32 bd = lbi + ((childDim / 2) * subDiv);

				UInt32 centre = bd + (childDim / 2);

				// Get the 4 Samples
				Float a = lpData[lbi];
				Float b = lpData[lti];
				Float c = lpData[rbi];
				Float d = lpData[rti];

				// Average out for the Center index
				Float avg = (a + b + c + d) / 4.0f;
				Float offset = Math::RandomFloat(-roughness, roughness);
				lpData[centre] = avg + offset;

				// Calculate the Diamond
				//Float childRoughness = roughness * desc.Rougness;
				Float ldv = ((lpData[lbi] + lpData[lti]) / 2.0f) + Math::RandomFloat(-roughness, roughness);
				Float rdv = ((lpData[rbi] + lpData[rti]) / 2.0f) + Math::RandomFloat(-roughness, roughness);
				Float tdv = ((lpData[lti] + lpData[rti]) / 2.0f) + Math::RandomFloat(-roughness, roughness);
				Float bdv = ((lpData[lbi] + lpData[rbi]) / 2.0f) + Math::RandomFloat(-roughness, roughness);

				// Set the Diamond
				lpData[ld] = ldv;
				lpData[rd] = rdv;
				lpData[td] = tdv;
				lpData[bd] = bdv;
			}
		}

		++depth;
		roughness *= desc.RoughnessFalloff;
		childDim /= 2;
	}
	// Blur the Data
	lpData = Filters::Blur(subDiv, subDiv, 1, 2, lpData);
	// Noise Variate the Data
	Float variation = 0.00001f;
	lpData = Filters::NoiseVariateAdd(subDiv, subDiv, 1, false, -variation, variation, 600.0f, lpData);
	// Normalize the Data
	lpData = Filters::Normalize(subDiv * subDiv, lpData);

	return lpData;
}